
/**
\file Definition of nmn::math::square_matrix.
\author Sean Tromans
*/

#ifndef	NMN_MATH_SQUARE_MATRIX_MAX_ARITY
#define	NMN_MATH_SQUARE_MATRIX_MAX_ARITY NMN_MATH_VECTOR_MAX_ARITY
#endif

#define NMN_MATH_MATRIX_SET_ARRAY( z, n, _ )	m_array[n] = BOOST_PP_CAT( p, n );

#define NMN_MATH_MATRIX_CONSTRUCTOR( z, n, _ )	\
	template < typename TValueType, size_t Size >	\
	template < BOOST_PP_ENUM_PARAMS_Z(z, BOOST_PP_INC(n), typename TVectorType) >	\
	inline square_matrix< TValueType, Size >::square_matrix( BOOST_PP_ENUM_BINARY_PARAMS_Z(z, BOOST_PP_INC(n), TVectorType, const & p) )	\
	{	\
		static_assert( static_size == BOOST_PP_INC(n), "Invalid number of parameters." );	\
		BOOST_PP_CAT( BOOST_PP_REPEAT_, z )( BOOST_PP_INC(n), NMN_MATH_MATRIX_SET_ARRAY, _ );	\
	}

namespace nmn
{

	namespace math
	{

		/**
		Default Constructor. Does nothing.
		\author Sean Tromans
		*/
		template < typename TValueType, size_t Size >
		inline square_matrix< TValueType, Size >::square_matrix( )
		{
		}

		/**
		Copy construct from another matrix of greater than or equal size, but potentially a different type.
		@param rhs The matrix to copy.
		\author Sean Tromans
		*/
		template < typename TValueType, size_t Size >
		template < typename TOtherValueType >
		inline square_matrix< TValueType, Size >::square_matrix( square_matrix< TOtherValueType, Size > const & rhs )
		{
			for ( size_t i = 0; i < Size; ++i )
			{
				m_array[i] = rhs.m_array[i];
			}
		}

		/**
		Construct a matrix from an array of vectors.
		@param values The array of vectors to copy.
		*/
		template < typename TValueType, size_t Size >
		template < typename TOtherValueType >
		inline square_matrix< TValueType, Size >::square_matrix( boost::array< nmn::math::vector< TOtherValueType, Size >, Size > const & values )
		{
			for ( size_t i = 0; i < Size; ++i )
			{
				m_array[i] = values[i];
			}
		}

		BOOST_PP_REPEAT_FROM_TO( 1, NMN_MATH_SQUARE_MATRIX_MAX_ARITY, NMN_MATH_MATRIX_CONSTRUCTOR, 0 );

		/**
		Assign this matrix to be equal to another.
		@param rhs The matrix to copy.
		\author Sean Tromans
		*/
		template < typename TValueType, size_t Size >
		template < typename TOtherValueType >
		inline typename square_matrix< TValueType, Size >::my_type & square_matrix< TValueType, Size >::operator = ( square_matrix< TOtherValueType, Size > const & rhs )
		{
			m_array = rhs.m_array;
			return *this;
		}

		/**
		The size of the matrix.
		\author Sean Tromans
		*/
		template < typename TValueType, size_t Size >
		inline typename square_matrix< TValueType, Size >::size_type square_matrix< TValueType, Size >::size( ) const
		{
			return static_size;
		}

		/**
		Component wise multiplication of another matrix onto this.
		@param rhs The matrix to be multiplied.
		@return A reference to this after the multiplication.
		\author Sean Tromans
		*/
		template < typename TValueType, size_t Size >
		inline typename square_matrix< TValueType, Size >::my_type & square_matrix< TValueType, Size >::operator *= ( typename square_matrix< TValueType, Size >::my_type const & rhs )
		{
			my_type lhs = transpose( *this );

			for ( size_t i = 0; i < size( ); ++i )
			{
				for ( size_t j = 0; j < size( ); ++j )
				{
					at(j).at(i) = dot( lhs[i], rhs[j] );
				}
			}

			return *this;
		}

		/**
		Component wise multiplication of a value type onto this.
		@param rhs The value type to be multiplied.
		@return A reference to this after the multiplication.
		\author Sean Tromans
		*/
		template < typename TValueType, size_t Size >
		inline typename square_matrix< TValueType, Size >::my_type & square_matrix< TValueType, Size >::operator *= ( typename square_matrix< TValueType, Size >::scalar_type const & rhs )
		{
			auto const begin = m_array.cbegin( );
			auto const cend = m_array.cend( );

			for ( auto iter = begin; iter != end; ++iter )
			{
				(*iter) *= rhs;
			}

			return *this;
		}

		/**
		Component wise strict equality check against another matrix.
		@param rhs The matrix to compare against.
		@return True if each corresponding component in the two matrices are exactly equal.
		\author Sean Tromans
		*/
		template < typename TValueType, size_t Size >
		bool square_matrix< TValueType, Size >::strict_equals( typename square_matrix< TValueType, Size >::my_type const & rhs ) const
		{
			return m_array == rhs.m_array;
		}

		/**
		Gets a column from the matrix.
		@param index The index of the column to return.
		@return a reference to the column at \c index.
		\author Sean Tromans
		*/
		template < typename TValueType, size_t Size >
		inline typename square_matrix< TValueType, Size >::reference_type square_matrix< TValueType, Size >::operator[] ( typename square_matrix< TValueType, Size >::size_type index )
		{
			return at(index);
		}

		/**
		Gets a column from the matrix.
		@param index The index of the column to return.
		@return a constant reference to the column at \c index.
		\author Sean Tromans
		*/
		template < typename TValueType, size_t Size >
		inline typename square_matrix< TValueType, Size >::const_reference_type square_matrix< TValueType, Size >::operator[] ( typename square_matrix< TValueType, Size >::size_type index ) const
		{
			return at(index);
		}

		/**
		Gets a column from the matrix.
		@param index The index of the column to return.
		@return a reference to the column at \c index.
		\author Sean Tromans
		*/
		template < typename TValueType, size_t Size >
		inline typename square_matrix< TValueType, Size >::reference_type square_matrix< TValueType, Size >::at( typename square_matrix< TValueType, Size >::size_type index )
		{
			return m_array.at(index);
		}

		/**
		Gets a column from the matrix.
		@param index The index of the column to return.
		@return a constant reference to the column at \c index.
		\author Sean Tromans
		*/
		template < typename TValueType, size_t Size >
		inline typename square_matrix< TValueType, Size >::const_reference_type square_matrix< TValueType, Size >::at( typename square_matrix< TValueType, Size >::size_type index ) const
		{
			return m_array.at(index);
		}

		/**
		Returns an iterator to the first column in the matrix.
		\author Sean Tromans
		*/
		template < typename TValueType, size_t Size >
		inline typename square_matrix< TValueType, Size >::iterator square_matrix< TValueType, Size >::begin( )
		{
			return m_array.begin( );
		}

		/**
		Returns a delimiting iterator to the matrix.
		\author Sean Tromans
		*/
		template < typename TValueType, size_t Size >
		inline typename square_matrix< TValueType, Size >::iterator square_matrix< TValueType, Size >::end( )
		{
			return m_array.end( );
		}

		/**
		Returns a constant iterator to the first column in the matrix.
		\author Sean Tromans
		*/
		template < typename TValueType, size_t Size >
		inline typename square_matrix< TValueType, Size >::const_iterator square_matrix< TValueType, Size >::begin( ) const
		{
			return cbegin( );
		}

		/**
		Returns a delimiting constant iterator to the matrix.
		\author Sean Tromans
		*/
		template < typename TValueType, size_t Size >
		inline typename square_matrix< TValueType, Size >::const_iterator square_matrix< TValueType, Size >::end( ) const
		{
			return cend( );
		}

		/**
		Returns a constant iterator to the first element in the matrix.
		\author Sean Tromans
		*/
		template < typename TValueType, size_t Size >
		inline typename square_matrix< TValueType, Size >::const_iterator square_matrix< TValueType, Size >::cbegin( ) const
		{
			return m_array.begin( );
		}

		/**
		Returns a delimiting constant iterator to the matrix.
		\author Sean Tromans
		*/
		template < typename TValueType, size_t Size >
		inline typename square_matrix< TValueType, Size >::const_iterator square_matrix< TValueType, Size >::cend( ) const
		{
			return m_array.end( );
		}

		/**
		Returns a reverse iterator to the last column in the matrix.
		\author Sean Tromans
		*/
		template < typename TValueType, size_t Size >
		inline typename square_matrix< TValueType, Size >::reverse_iterator square_matrix< TValueType, Size >::rbegin( )
		{
			return m_array.rbegin( );
		}

		/**
		Returns a delimiting reverse iterator to the matrix.
		\author Sean Tromans
		*/
		template < typename TValueType, size_t Size >
		inline typename square_matrix< TValueType, Size >::reverse_iterator square_matrix< TValueType, Size >::rend( )
		{
			return m_array.rend( );
		}

		/**
		Returns a constant reverse iterator to the last column in the matrix.
		\author Sean Tromans
		*/
		template < typename TValueType, size_t Size >
		inline typename square_matrix< TValueType, Size >::const_reverse_iterator square_matrix< TValueType, Size >::rbegin( ) const
		{
			return m_array.rbegin( );
		}

		/**
		Returns a delimiting constant reverse iterator to the matrix.
		\author Sean Tromans
		*/
		template < typename TValueType, size_t Size >
		inline typename square_matrix< TValueType, Size >::const_reverse_iterator square_matrix< TValueType, Size >::rend( ) const
		{
			return m_array.rend( );
		}

		/**
		Swaps this matrix with another.
		@param rhs The matrix to swap with.
		\author Sean Tromans
		*/
		template < typename TValueType, size_t Size >
		void square_matrix< TValueType, Size >::swap( typename square_matrix< TValueType, Size >::my_type & rhs )
		{
			m_array.swap( rhs.m_array );
		}

		/**
		\relates nmn::math::square_matrix
		Outputs a square_matrix to an output stream.
		@param os The output ostream.
		@param vec The square_matrix to output.
		@return A reference to the output stream.
		\author Sean Tromans
		*/
		template < typename Elem, typename Traits, typename TValueType, size_t Size >
		inline std::basic_ostream< Elem, Traits > & operator << ( std::basic_ostream< Elem, Traits > & os, square_matrix< TValueType, Size > const & matrix )
		{
			os << "{ ";
			std::copy(	matrix.begin(), matrix.end(), std::ostream_iterator< square_matrix< TValueType, Size >::value_type >( os, " " ) );
			os << " }";

			return os;
		}

		/**
		\relates nmn::math::square_matrix
		Component wise absolute equality check.
		@param lhs The first square_matrix to compare.
		@param rhs The second square_matrix to compare.
		@return true if the two square_matrixs are exactly equal.
		\author Sean Tromans
		*/
		template < typename TValueType, size_t Size >
		bool operator == ( square_matrix< TValueType, Size > const & lhs, square_matrix< TValueType, Size > const & rhs )
		{
			return lhs.strict_equals( rhs );
		}

		/**
		\relates nmn::math::square_matrix
		Linear algebra multiplication of a vector with a matrix.
		@param lhs The matrix to be multiplied.
		@param rhs The vector to be multiplied.
		@return A reference to this after the multiplication.
		\author Sean Tromans
		*/
		template< typename TValueType, typename TOtherValueType, std::size_t Size >
		inline vector< TOtherValueType, Size >
			operator * (	square_matrix< TValueType, Size > lhs,
							vector< TOtherValueType, Size > const & rhs )
		{
			lhs = transpose( lhs );

			square_matrix< TOtherValueType, Size >::value_type out;

			for ( size_t i = 0; i < Size; ++i )
			{
				out[i] = dot( rhs, lhs[i] );
			}

			return out;
		}

	}

}

#undef NMN_MATH_MATRIX_CONSTRUCTOR
